﻿using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Security.Permissions;


namespace WinRun
{
    /// <summary>
    /// A lot of this came from the PInvoke.net site
    /// </summary>
    /// <see cref="http://www.pinvoke.net/default.aspx/user32/RegisterHotKey.html"/>
    public sealed class KeyboardHook : IDisposable
    {
        [FlagsAttribute]
        public enum ModifierKeycodes : int
        {
            MOD_ALT = 1,
            MOD_CONTROL = 2,
            MOD_SHIFT = 4,
            MOD_WIN = 8
        }

        public const int WM_HOTKEY = 0x312;

        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = false)]
        public KeyboardHook()
        {
            this.Handle = Process.GetCurrentProcess().Handle;
        }

        /// <summary> handle of the current process </summary>
        public IntPtr Handle;

        /// <summary> the ID for the hotkey </summary>
        public short HotkeyID
        {
            get;
            private set;
        }

        /// <summary> register the hotkey </summary>
        public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
        {
            /** Just in case its already registered. I suppose the responsible thing to do would be to look at the list before stepping on another app **/
            UnregisterGlobalHotKey();

            /** Handle of the window that called this **/
            this.Handle = handle;

            /** register **/
            RegisterGlobalHotKey(hotkey, modifiers);
        }

        /// <summary>
        /// Registers the hotkey with Windows
        /// </summary>
        /// <param name="hotkey">the int value of the keycode</param>
        /// <param name="modifiers">any modifier keys</param>
        public void RegisterGlobalHotKey(int hotkey, int modifiers)
        {
            UnregisterGlobalHotKey();

            try
            {
                // use the GlobalAddAtom API to get a unique ID (as suggested by MSDN docs)
                string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + this.GetType().FullName;
                HotkeyID = NativeMethods.GlobalAddAtom(atomName);

                /** Make sure it was added **/
                if (HotkeyID == 0)
                {
                    throw new Exception("Unable to generate unique hotkey ID. Error: " + Marshal.GetLastWin32Error().ToString());
                }

                /** Clear any modifiers from the desired hotkey **/
                Keys k = (Keys)hotkey & ~Keys.Control & ~Keys.Shift & ~Keys.Alt;

                /** actual call **/
                bool dd = NativeMethods.RegisterHotKey(this.Handle, HotkeyID, modifiers, (int)k);

                if (!dd)
                {
                    throw new Exception("Unable to register hotkey. Error: " + Marshal.GetLastWin32Error().ToString());
                }


            }
            catch (Exception)
            {
                // clean up if hotkey registration failed
                UnregisterGlobalHotKey();
            }
        }

        /// <summary> unregister the hotkey </summary>
        public void UnregisterGlobalHotKey()
        {
            if (this.HotkeyID != 0)
            {
                NativeMethods.UnregisterHotKey(this.Handle, HotkeyID);
                // clean up the atom list
                NativeMethods.GlobalDeleteAtom(HotkeyID);
                HotkeyID = 0;
            }
        }

        /// <summary>
        /// Make sure we clean the hook before we exit
        /// </summary>
        public void Dispose()
        {
            UnregisterGlobalHotKey();
        }

    }
}
